package com.sf.ams.biz.impl;

import java.io.File;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.sf.ams.biz.IDBConnectorBiz;
import com.sf.ams.biz.IDataStoreXmlAnalysisBiz;
import com.sf.ams.biz.IExecutorBiz;
import com.sf.ams.constant.Constant;
import com.sf.ams.entity.DBConnectorParamter;
import com.sf.ams.entity.Environment;
import com.sf.ams.entity.Response;
import com.sf.ams.entity.SystemInfo;
import com.sf.ams.entity.enums.ExecuteStatus;
import com.sf.ams.entity.enums.SystemExceptionType;
import com.sf.ams.exception.SqlFileHandlerException;
import com.sf.ams.sqlfile.BaseSqlFileHandler;
import com.sf.ams.sqlfile.FileSqlFileHandler;
import com.sf.ams.sqlfile.GitSqlFileHandler;
import com.sf.ams.utils.AESUtil;
import com.sf.ams.utils.DateUtil;
import com.sf.ams.utils.ExecutorStatusMapUtil;
import com.sf.ams.utils.FileUtil;
import com.sf.ams.utils.XMLWriterUtil;

/**
 * 描述：
 * 
 * <pre>HISTORY
 * ****************************************************************************
 *  ID   DATE           PERSON          REASON
 *  1    2017年9月28日      Simba.Hua         Create
 * ****************************************************************************
 * </pre>
 * @author Simba.Hua
 */
@Service("executorBiz")
public class ExecutorBizImpl implements IExecutorBiz{
	@Autowired
	private IDataStoreXmlAnalysisBiz dataStoreXmlAnalysisBiz;
	@Autowired
	private IDBConnectorBiz dBConnectorBiz;
	private static final Map<String,Future<Response>> reponses = new ConcurrentHashMap<>();
	private static final ExecutorService threadpool = Executors.newCachedThreadPool();
	private Logger logger = LoggerFactory.getLogger(getClass());
	@Override
	public boolean execute(SystemInfo system) {
		SystemInfo trueSystem = dataStoreXmlAnalysisBiz.getSystemBySystemCode(system.getSystemCode(),true);
		if(trueSystem == null)return false;
		
		List<Environment> oldEnvironments = system.getEnvironments();
		List<Environment> environments = new ArrayList<>();
		oldEnvironments.forEach(e -> 
			environments.addAll(trueSystem.getEnvironments().stream()
					.filter(te -> te.getEnvironmentType().equals(e.getEnvironmentType()))
					.collect(Collectors.toList()))
		);
		 List<File> sqlFiles = new ArrayList<>() ;
		BaseSqlFileHandler sqlFileHander = null;
		if (Constant.GIT_SQL_PATH.equals(trueSystem.getPathType())) {
			sqlFileHander = new GitSqlFileHandler(environments,trueSystem);
		} else {
			sqlFileHander = new FileSqlFileHandler(environments,trueSystem);
		}
		try {
			sqlFiles.addAll(sqlFileHander.processSqlFile());
		} catch (SqlFileHandlerException e2) {
			logger.error("SqlFileHandlerException",e2);
			return false;
		}
		CountDownLatch environmentCount = new CountDownLatch(environments.size());
		environments.forEach(e -> {
			Future<Response> response = threadpool.submit(new Callable<Response>(){
			@Override
			public Response call() throws Exception {
				//把文件内容转换成String放入map中
				Map<Integer,String> sqls = new HashMap<>();
				Integer nextIndex = 0;
				if(e.getCurrentStatus() == ExecuteStatus.SUCCESS){
					nextIndex = e.getCurrentIndex() + 1;
					sqls = FileUtil.transferFileToString(sqlFiles, nextIndex);
				} else if (e.getCurrentStatus() == ExecuteStatus.FAIL || e.getCurrentStatus() == ExecuteStatus.INIT) {
					nextIndex = e.getCurrentIndex();
					sqls = FileUtil.transferFileToString(sqlFiles, nextIndex);
				}
				
				Response response = dBConnectorBiz.executeBatchSql(sqls,nextIndex,getDataBaseConnection(e,system));
				//计算完一个，计数器减一
				environmentCount.countDown();
				return response;
			}
				
			});
			reponses.put(system.getSystemCode() + "_" + e.getEnvironmentType(), response);
			
		});
		try {
			environmentCount.await();
			return true;
		} catch (InterruptedException e1) {
			logger.error("environmentCount await error",e1);
			 Thread.currentThread().interrupt();
			 return false;
		}
	}
	
	@Override
	public List<Environment> refreshStatus(SystemInfo system){
		List<Environment> oldEnvironment = system.getEnvironments();
		for (Environment e : oldEnvironment) {
			String key = system.getSystemCode() + "_" + e.getEnvironmentType();
			Future<Response> future = reponses.get(key);
			if (future == null ) {
				processEnvironmentException(e,system.getSystemCode(),e.getEnvironmentType());
				continue;
			}
			if (future.isDone()) {
				processHaveDone(future,e);
				reponses.remove(key);
				XMLWriterUtil.getInstance().updateXml(updateNodePath(system.getSystemCode(),e.getEnvironmentType()), updateElement(e));//更新文件内容
				ExecutorStatusMapUtil.setExecutorStauts(key, e.getCurrentStatus() == ExecuteStatus.FAIL?SystemExceptionType.FAIL:SystemExceptionType.SUCCESS);
			} else {
				e.setCurrentStatus(ExecuteStatus.EXECUTING);
			}
		}
		return oldEnvironment;
	}
	/**
	 * 处理已经执行完的环境
	 * @param future
	 * @param e
	 */
	private void processHaveDone(Future<Response> future,Environment e){
		try {
			Response response =  future.get();
			if(response != null) {
				if (response.getCurrentIndex() != -1){
					e.setCurrentIndex(response.getCurrentIndex());
				}
				
				e.setExecuteFailReason("");
				//当脚本执行失败时的一些处理
				executeSqlFail(response,e);
				
				e.setCurrentStatus(response.getStatus().equals(Response.STATUS_SUCCESS)?ExecuteStatus.SUCCESS : ExecuteStatus.FAIL);
			} else {
				e.setCurrentStatus(ExecuteStatus.SUCCESS);
			}
			e.setLastExecuteTime(DateUtil.formatDate(new Date(), DateUtil.DATE_PATTERN_YYYY_MM_DD_HH_MM_SS));
			logger.info("currentIndex:{},currentStatus:{}",e.getCurrentIndex(),e.getCurrentStatus());
		} catch (Exception e1) {
			logger.error("get future fail",e1);
		}
	}
	
	/**
	 * 当脚本执行失败时的一些处理
	 * @param response
	 * @param e
	 */
	private void executeSqlFail(Response response,Environment e){
		if(response.getStatus().equals(Response.STATUS_FAIL)) {
			//没有更大的值时需要判断之前是否是成功，如果之前是成功，那么这次失败了当前索引就要+1，如果不+1则会重复执行上一次的脚本
			if(response.getFailReason().equals("NoBigIndex")){
				Integer nextIndex = e.getCurrentIndex();
				e.setCurrentIndex(nextIndex);
				e.setExecuteFailReason("没有大于等于索引【"+nextIndex+"】的值，无脚本可以执行");
			} else {
				e.setExecuteFailReason(response.getFailReason());
			}
		}
	}
	/**
	 * 处理环境运行中的异常
	 * @param e
	 * @param key
	 */
	private void processEnvironmentException(Environment e,String systemCode,String eType){
		String key = systemCode + "_" + eType;
		SystemExceptionType exceptionType = ExecutorStatusMapUtil.getExecutorStatus(key);
		if (exceptionType == SystemExceptionType.COMMONING) {
			e.setCurrentStatus(ExecuteStatus.GITTING);
		} else if (exceptionType == SystemExceptionType.GITEXCEPTION) {
			e.setCurrentStatus(ExecuteStatus.FAIL);
			e.setExecuteFailReason("从GIT中更新代码失败，请检查GIT的地址、用户名和密码!");
			e.setLastExecuteTime(DateUtil.formatDate(new Date(), DateUtil.DATE_PATTERN_YYYY_MM_DD_HH_MM_SS));
			XMLWriterUtil.getInstance().updateXml(updateNodePath(systemCode,eType), updateElement(e));//更新文件内容
		} else if (exceptionType == SystemExceptionType.ZIPEXCEPTION) {
			e.setCurrentStatus(ExecuteStatus.FAIL);
			e.setExecuteFailReason("解压文件包失败，请检查您上传的文件包!");
			e.setLastExecuteTime(DateUtil.formatDate(new Date(), DateUtil.DATE_PATTERN_YYYY_MM_DD_HH_MM_SS));
			XMLWriterUtil.getInstance().updateXml(updateNodePath(systemCode,eType), updateElement(e));//更新文件内容
		} else if (exceptionType == SystemExceptionType.SQLPATHEXCPTION) {
			e.setCurrentStatus(ExecuteStatus.FAIL);
			e.setExecuteFailReason("SQL 脚本路径不存在!");
			e.setLastExecuteTime(DateUtil.formatDate(new Date(), DateUtil.DATE_PATTERN_YYYY_MM_DD_HH_MM_SS));
			XMLWriterUtil.getInstance().updateXml(updateNodePath(systemCode,eType), updateElement(e));//更新文件内容
		} else if (exceptionType == SystemExceptionType.NOSQLEXCEPTION) {
			e.setCurrentStatus(ExecuteStatus.FAIL);
			e.setExecuteFailReason("无执行脚本");
			e.setLastExecuteTime(DateUtil.formatDate(new Date(), DateUtil.DATE_PATTERN_YYYY_MM_DD_HH_MM_SS));
			XMLWriterUtil.getInstance().updateXml(updateNodePath(systemCode,eType), updateElement(e));//更新文件内容
		} else if(exceptionType == SystemExceptionType.SUCCESS) {
			e.setCurrentStatus(ExecuteStatus.SUCCESS);
		} else if(exceptionType == SystemExceptionType.FAIL) {
			e.setCurrentStatus(ExecuteStatus.FAIL);
		}
	}
	private Map<String,String> updateElement(Environment e){
		Map<String,String> result = new HashMap<>();
		result.put("currentIndex", e.getCurrentIndex().toString());
		result.put("lastExecuteTime", DateUtil.formatDate(new Date(), DateUtil.DATE_PATTERN_YYYY_MM_DD_HH_MM_SS));
		result.put("currentStatus", e.getCurrentStatus().name());
		result.put("executeFailReason", e.getExecuteFailReason());
		return result;
	}
	private String updateNodePath(String systemCode,String etype){
		StringBuilder sb = new StringBuilder();
		sb.append("/automaticSql/systems/system[systemCode='")
		  .append(systemCode)
		  .append("']/environments/environment[environmentType='")
		  .append(etype)
		  .append("']");
		return sb.toString();
	}
	private String getDriverByDBType(String dbType){
		if("ORACLE".equalsIgnoreCase(dbType)) {
			return Constant.DRIVER_ORACLE;
		}
		if("MYSQL".equalsIgnoreCase(dbType)) {
			return Constant.DRIVER_MYSQL;
		}
		return "";
	}
	private Connection getDataBaseConnection(Environment e,SystemInfo system){
		DBConnectorParamter connectorParam = new DBConnectorParamter(getDriverByDBType(e.getDataBaseType()), 
				e.getDataBaseConnection() + (Constant.DATABASE_MYSQL.equalsIgnoreCase(e.getDataBaseType())?
																				Constant.DATABASE_CHARACTOR:""), 
				e.getDataBaseUserName(),
				AESUtil.AESDncode(system.getSystemCode(), e.getDataBasePassword()));
		
		return dBConnectorBiz.getConnection(connectorParam);
	}

}
